Challenge: ccv1

Deep Learning: Was versteckt sich da?¶

Explorative Datenanalyse¶

In [ ]:
%matplotlib inline

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from PIL import Image
import matplotlib.image as mpimg
from tqdm import tqdm

from IPython.display import HTML, display

Inhalt:

  1. CSV Daten lesen (Train, Test, Label)
  2. Die Klassifikation Tierarten
  3. Bilddaten Lesen (Train)
    1. Benchmark Ansicht
    2. Random Ansicht
    3. Problematische Bilder
  4. Verteilungen der Tierklassen
  5. Analyse zu Kamera Standorte (site ID)
    1. Verteilung der Bilder je Standort (Train)
    2. Plotten der Bilder je Standort
  6. Analyse zur Bildauflösung
    1. Bilder und Bittiefe
  7. Spezifische Kamermerkmale
    1. Bild bearbeitung durch zuschneiden
    2. Weitere Merkmale
    3. Analysieren von Kamerastandorte
  8. Augmentation Test

CSV Daten Lesen (Train, Test, Label)¶

Die CSV's beinhalten die Bild ID, Bildpfad, Aufnahmeort ID und die Tierklassifikation (Training)

In [ ]:
train_features = pd.read_csv("../competition_data/train_features.csv", index_col="id")
test_features = pd.read_csv("../competition_data/test_features.csv", index_col="id")
train_labels = pd.read_csv("../competition_data/train_labels.csv", index_col="id")

display(train_features.head())
display(train_labels.head())
filepath site
id
ZJ000000 train_features/ZJ000000.jpg S0120
ZJ000001 train_features/ZJ000001.jpg S0069
ZJ000002 train_features/ZJ000002.jpg S0009
ZJ000003 train_features/ZJ000003.jpg S0008
ZJ000004 train_features/ZJ000004.jpg S0036
antelope_duiker bird blank civet_genet hog leopard monkey_prosimian rodent
id
ZJ000000 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
ZJ000001 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0
ZJ000002 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
ZJ000003 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0
ZJ000004 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0
In [ ]:
# reverse One-Hot-Encoding
species_labels = sorted(train_labels.columns.unique())

train_labels_cat = train_labels.copy()
train_labels_cat['label'] = train_labels[species_labels].idxmax(axis=1)
train_labels_cat = train_labels_cat.drop(species_labels, axis=1)

display(train_labels_cat.head())
label
id
ZJ000000 bird
ZJ000001 monkey_prosimian
ZJ000002 bird
ZJ000003 monkey_prosimian
ZJ000004 leopard

Die Klassifikation Tierarten¶

Klassifikation findet für 8 Tierarten statt

In [ ]:
print(species_labels)
print(f'Anzahl Tierklassen: {len(species_labels)}')
['antelope_duiker', 'bird', 'blank', 'civet_genet', 'hog', 'leopard', 'monkey_prosimian', 'rodent']
Anzahl Tierklassen: 8
In [ ]:
img = mpimg.imread('class_images/class_images.jpg')
plt.imshow(img)
plt.axis('off')
plt.show()

Die Klassen 'bird', 'rodent' umfassen mehrere Tierartren. Die Klasse'blank' steht für ein Bild ohne ein Tier. Die übrigen Tierklassen sind im obigen Bild enthalten.

Bilddaten Lesen (Train)¶

Probeansicht Bilder (quelle: benchmark file)

In [ ]:
random_state = 42
path_img = '../competition_data/'

# we'll create a grid with 8 positions, one for each label (7 species, plus blanks)
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(20, 20))

# iterate through each species
for species, ax in zip(species_labels, axes.flat):
    # get an image ID for this species
    img_id = (
        train_labels[train_labels.loc[:,species] == 1]
        .sample(1, random_state=random_state)
        .index[0]
    )
    # reads the filepath and returns a numpy array
    img = mpimg.imread(path_img + train_features.loc[img_id].filepath)
    # plot etc
    ax.imshow(img)
    ax.set_title(f"{img_id} | {species}")

Random Ansicht¶

Zelle mehrmals ausführen um unterschiedliche Bilder zu erhalten

In [ ]:
# we'll create a grid with 8 positions, one for each label (7 species, plus blanks)
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(20, 20))

# iterate through each species
for species, ax in zip(species_labels, axes.flat):
    # get an image ID for this species
    img_id = (
        train_labels[train_labels.loc[:,species] == 1]
        .sample(1)
        .index[0]
    )
    # reads the filepath and returns a numpy array
    img = mpimg.imread(path_img + train_features.loc[img_id].filepath)
    # plot etc
    ax.imshow(img)
    ax.set_title(f"{img_id} | {species}")

Problematische Bilder¶

Folgend wurden spezifisch Bilder herausgesucht um die Problematik von schlechten Bilder zu zeigen.

In [ ]:
example_bad_img = ['ZJ015580', 'ZJ002746', 'ZJ007054', 'ZJ000888', 'ZJ010341', 'ZJ014451',
                   'ZJ004927', 'ZJ007091', 'ZJ013234', 'ZJ013093', 'ZJ004451', 'ZJ010190',
                   'ZJ002138', 'ZJ002196']
example_good_img = ['ZJ003890', 'ZJ004925', 'ZJ012762', 'ZJ014396', 'ZJ015264', 'ZJ000895',
                    'ZJ007334', 'ZJ004978', 'ZJ014157', 'ZJ010885']


def plot_image_from_image_id(image_ids: list, nrows=4, ncols=3, figsize=(15, 15),
                             fontsize=10, path='../competition_data/'):
    # create grid 
    fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize)

    # iterate through each bad image
    for idx, (img_id, ax) in enumerate(zip(image_ids, axes.flat)):
        # get image label
        img_label = train_labels_cat.loc[image_ids[idx]]['label']
        # reads the filepath and returns a numpy array
        img = mpimg.imread(path + train_features.loc[img_id].filepath)
        # image dimension
        width, height = Image.open(path_img + train_features.loc[img_id].filepath).size
        # plot etc
        ax.imshow(img)
        ax.set_title(f"{img_id} | {img_label} | {str(width)}x{str(height)}", fontsize=fontsize)

plot_image_from_image_id(example_bad_img)
#plot_image_from_image_id(example_good_img)

Zu Prüfen: wenn die Bilder im Notebook mit den Bilder im File Ordner verglichen werden, fällt auf dass die Bilder in der Windowsansicht schwarz-weiss im Notebook blau-gelb angezeigt werden. Beispiel 'ZJ003494'.
Interpretation von mpimg prüfen.

Verteilungen der Tierklassen¶

(Quelle: Benchmark) Wie im Benchmark Notebook beschrieben, entspricht die Tierklassen Verteilungen nicht dem eigentlichen Vorkommen. Die (Trainings)daten wurden für die Competition bereits vorbereitet.

In [ ]:
display(train_labels.sum().sort_values(ascending=False))
display(train_labels.sum().divide(train_labels.shape[0]).sort_values(ascending=False))
monkey_prosimian    2492.0
antelope_duiker     2474.0
civet_genet         2423.0
leopard             2254.0
blank               2213.0
rodent              2013.0
bird                1641.0
hog                  978.0
dtype: float64
monkey_prosimian    0.151140
antelope_duiker     0.150049
civet_genet         0.146955
leopard             0.136705
blank               0.134219
rodent              0.122089
bird                0.099527
hog                 0.059316
dtype: float64

Analyse zu Kamera Standorte (site ID)¶

In [ ]:
train_features.head(2)
Out[ ]:
filepath site
id
ZJ000000 train_features/ZJ000000.jpg S0120
ZJ000001 train_features/ZJ000001.jpg S0069
In [ ]:
print(f'Anzahl Standort Training-Kameras: {len(train_features.site.unique())}')  
print(f'Anzahl Standort Test-Kameras: {len(test_features.site.unique())}')

print(f'Mittelwert: {len(train_features.site) / len(train_features.site.unique()):.0f} Bilder pro Ort, Train')
print(f'Mittelwert: {len(test_features.site) / len(test_features.site.unique()):.0f} Bilder pro Ort, Test')
Anzahl Standort Training-Kameras: 148
Anzahl Standort Test-Kameras: 51
Mittelwert: 111 Bilder pro Ort, Train
Mittelwert: 88 Bilder pro Ort, Test

Verteilung der Bilder je Standort (Train)¶

In [ ]:
df_site_count = train_features.groupby('site').count().reset_index().sort_values('filepath', ascending=True)

# plot histgram
plt.figure(figsize=(20, 10))
plt.bar(df_site_count.site, df_site_count.filepath)
plt.title('Anzahl Bilder je Kamera Standort (Train)')
plt.xlabel('site ID')
plt.ylabel('counts')
plt.xticks(rotation=90)
plt.grid()
plt.show()

Insgesamt bestehen 148 verschiedene Kamera Standorte. Die ID-Nummerierung verläuft von S0001 bis S0198 (ID-Nummerierung nicht komplet durch numeriert). Die Verteilung zeigt die Anzahl Bilder die für einen Standort zur Verfügung stehen. Einige Standorte nehmen nur sehr wenige Bilder auf (1-2) ander enthalten hunderte Bilder.

Verteilung der Tierklassen je Standort¶

In [ ]:
df_train_feature_labels = train_features.merge(train_labels_cat, left_index=True, right_index=True)
df_train_feature_labels.head()
Out[ ]:
filepath site label
id
ZJ000000 train_features/ZJ000000.jpg S0120 bird
ZJ000001 train_features/ZJ000001.jpg S0069 monkey_prosimian
ZJ000002 train_features/ZJ000002.jpg S0009 bird
ZJ000003 train_features/ZJ000003.jpg S0008 monkey_prosimian
ZJ000004 train_features/ZJ000004.jpg S0036 leopard
In [ ]:
df_stacked_plot = pd.crosstab(df_train_feature_labels['site'], df_train_feature_labels['label'])
df_stacked_plot['max_count'] = df_stacked_plot.sum(axis=1)
df_stacked_plot = df_stacked_plot.sort_values('max_count', ascending=True)
df_stacked_plot.head()
Out[ ]:
label antelope_duiker bird blank civet_genet hog leopard monkey_prosimian rodent max_count
site
S0102 0 0 0 0 0 0 1 0 1
S0078 0 0 2 0 0 0 0 0 2
S0079 0 0 2 0 0 0 0 0 2
S0178 0 0 0 0 0 2 0 0 2
S0143 1 0 2 0 0 0 0 0 3
In [ ]:
def plot_stacked_classes_site(stacked_plot, min_count = 50):
    stacked_plot = stacked_plot[stacked_plot['max_count'] > min_count]
    stacked_plot = stacked_plot.drop(columns='max_count')


    stacked_plot.plot(kind='bar', stacked=True, figsize=(20,10))
    plt.title('Vorkommen Tierklassen je Standort (Train)')
    plt.xlabel('site ID')
    plt.ylabel('counts')
    plt.xticks(rotation=90)
    plt.grid()
    plt.show()

plot_stacked_classes_site(df_stacked_plot, 50)

Plotten der Bilder je Standort¶

In [ ]:
train_features[train_features['site'] == 'S0007']
Out[ ]:
filepath site
id
ZJ003948 train_features/ZJ003948.jpg S0007
ZJ006104 train_features/ZJ006104.jpg S0007
ZJ007333 train_features/ZJ007333.jpg S0007
ZJ007590 train_features/ZJ007590.jpg S0007
ZJ008782 train_features/ZJ008782.jpg S0007
ZJ010893 train_features/ZJ010893.jpg S0007
ZJ012885 train_features/ZJ012885.jpg S0007
ZJ013315 train_features/ZJ013315.jpg S0007
ZJ013350 train_features/ZJ013350.jpg S0007
In [ ]:
def plot_image_from_site_id(site_id: str, nrows=4, ncols=3, figsize=(15, 15), path='../competition_data/'):
    # get images from site
    img_site = train_features[train_features['site'] == site_id].reset_index()
    img_site = img_site[0:nrows*ncols]
    # create grid 
    fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize)

    # iterate through each bad image
    for img_id, image_path, ax in zip(img_site.id, img_site.filepath, axes.flat):
        # reads the filepath and returns a numpy array
        img = mpimg.imread(path + image_path)
         # get image label
        img_label = train_labels_cat.loc[img_id]['label']
        # plot
        ax.imshow(img)
        ax.set_title(f"{img_id} | {img_label}")

plot_image_from_site_id('S0007')

Analyse zu Bildauflösung¶

Hier soll geprüft werden wie stark die Auflösungen der Kameras varrieren.

Die Auflösungen kann mit 'mpimg' durch shape wiedergegeben werden. Eine schneller Möglichkeit ist 'Pillow' zu verwenden, die Bittiefe muss jedoch seperate mit 'getbands()' ausgelesen werden.

getbands()

In [ ]:
mode_to_bpp = {'1':1, 'L':8, 'P':8, 'RGB':24, 'RGBA':32, 'CMYK':32, 'YCbCr':24, 'I':32, 'F':32}

img_id = []
img_width = []
img_height = []
img_dim = []
band_mode = []
bit_depth = []

for imag_id in train_features.reset_index().id:
    # get id and dimensions
    img = Image.open(path_img + train_features.loc[imag_id].filepath)
    width, height = img.size
    dim = img.size
    mode = img.getbands()

    # save to list
    img_id.append(imag_id)
    img_width.append(width)
    img_height.append(height)
    img_dim.append(dim)
    band_mode.append(mode)

df_img_shape_pil = pd.DataFrame({'id': img_id, 'width': img_width, 'height': img_height, 
                                 'dim':img_dim, 'band_mode': band_mode})

df_img_shape_pil.head(2)
Out[ ]:
id width height dim band_mode
0 ZJ000000 960 540 (960, 540) (R, G, B)
1 ZJ000001 960 540 (960, 540) (R, G, B)
In [ ]:
unique_dim = df_img_shape_pil.dim.value_counts().reset_index().sort_values('dim', ascending=False)
unique_dim = unique_dim.rename(columns={'dim': 'count', 'index': 'dim'})
unique_dim['count_relativ'] = np.round(unique_dim['count'] / sum(unique_dim['count']), 2)
#display(unique_dim)
In [ ]:
fig, ax = plt.subplots(1, 2, figsize=(8, 5))

ax[0].bar(np.arange(len(unique_dim)), unique_dim['count'])
ax[0].set_xticks(np.arange(len(unique_dim)), unique_dim.dim)
ax[0].set_title('Verteilung der Bildauflösungen')
ax[0].set_ylabel('counts')
ax[0].set_xticklabels(unique_dim.dim, rotation=60)

for i, v in enumerate(unique_dim['count']):
    ax[0].text(i, v, str(v), ha='center', va='bottom')

ax[1].bar(np.arange(len(unique_dim)), unique_dim['count_relativ'] )
ax[1].set_xticks(np.arange(len(unique_dim)), unique_dim.dim)
ax[1].set_title('Verteilung der Bildauflösungen [relativ]')
ax[1].set_ylabel('counts')
ax[1].set_xticklabels(unique_dim.dim, rotation=60)


for i, v in enumerate(unique_dim['count_relativ']):
    ax[1].text(i, v, str(v), ha='center', va='bottom')

plt.show()

Über 80% der Bilder haben die Auflösung (640, 360) oder (960, 540)

Bilder und Bittiefe¶

Untersuchen der Bittiefe

In [ ]:
df_bit_depth = df_img_shape_pil['band_mode'].value_counts().reset_index(name='count')
df_bit_depth = df_bit_depth.rename(columns={'index':'mode'})
df_bit_depth.plot(x='mode', y='count', kind='bar')
plt.suptitle('Verteilung Bit Tiefe Bilder', fontsize=12)
plt.title('RGB: 24bit, L: 8bit', fontsize=8)
plt.xlabel('band mode')
plt.ylabel('count')
plt.xticks(rotation=0)
plt.grid()
plt.show()
In [ ]:
 

Spezifische Kameramerkmale¶

Am unteren Rand haben viele Bilder das gleiche Logo und Zeitstempfel. Auf verschiedenen Bildern wurden diese teilweise durch beschneiden entfernt. Hier soll untersucht werden:

  • ob Aussagen über die Bearbeitung gemacht werden können (z.B. unterer Teil entfert)?
  • kann durch die Auflösung auf eine Bearbeitung geschlossen werden?
  • habe Standort bestimmte Eigenschaft?

Bild bearbeitung durch zuschneiden¶

In [ ]:
# images with Logo and time:
img_logo_time = ['ZJ000001', 'ZJ000002', 'ZJ000003', 'ZJ000005',
                 'ZJ000006', 'ZJ000025', 'ZJ000026', 'ZJ000031', 'ZJ000140']


plot_image_from_image_id(img_logo_time, nrows=3, figsize=(10, 7), fontsize=8)

Die neun Testbilder gehören alle zu beiden Hauptgruppen der Bildauflösungen, (640x360) und (960x540)

In [ ]:
# images cut
img_logo_time_cut = ['ZJ000004', 'ZJ000053', 'ZJ000063', 'ZJ000090',
                 'ZJ000099', 'ZJ000106', 'ZJ000114', 'ZJ000119', 'ZJ000156']

plot_image_from_image_id(img_logo_time_cut, nrows=3, figsize=(10, 7), fontsize=8)

Die neun Testbilder, bei denen das Logo und Zeitstempfel teilweise entfernt wurde, haben jeweils eine Beschneidung in der Dimension 'height'. Daraus könnte geschlossen werden dass die bearbeiteten Bilder der Hauptgruppe angehören jedoch mit fehlendem unterem Abschnitt, (640x360) -> (640x335) und (960x540) -> (960x515)

In [ ]:
# special cases
img_special_case = ['ZJ000019', 'ZJ000015', 'ZJ000016', 'ZJ000018',
                 'ZJ000065', 'ZJ000124', 'ZJ000132', 'ZJ000139', 'ZJ000142']

plot_image_from_image_id(img_special_case, nrows=3, figsize=(10, 7), fontsize=8)

Weitere Bildauflösungs Merkmale¶

In [ ]:
 

Analysieren von Kamerastandorte¶

In [ ]:
df_site_count[df_site_count['filepath'] > 400] 
Out[ ]:
site filepath
88 S0120 423
32 S0038 429
45 S0059 438
34 S0043 444
31 S0036 456
7 S0008 541
49 S0063 557
8 S0009 664
46 S0060 1132
In [ ]:
plot_image_from_site_id('S0106')
In [ ]:
 

Augmentation Test¶

Hier soll versucht werden ob das Logo im linken unteren Teil des Bildes automatisch erkannt werden kann

In [ ]:
# crop image
def crop_image_for_logo(pillow_image, plot_image=False):
    # dim img
    width, height = img.size

    # crop images, left corner
    crop_height = height * 0.09
    crop_width = width * 0.95

    x1 = 0
    y1 = height - crop_height
    x2 = width - crop_width
    y2 = height

    cropped_img = img.crop((x1, y1, x2, y2))

    if plot_image:
        plt.imshow(cropped_img)
        plt.show()

    return cropped_img

# get image main color
def get_img_main_color(pillow_imag, print_info=False):
    # Wandle das Bild in den RGB-Modus um, falls es noch nicht im RGB-Modus vorliegt.
    if pillow_imag.mode != "RGB":
        pillow_imag = pillow_imag.convert("RGB")

    # Extrahiere die Farb-Kanäle aus dem Bild.
    r, g, b = pillow_imag.split()[0], pillow_imag.split()[1], pillow_imag.split()[2]

    # Berechne den durchschnittlichen Wert des Kanals.
    mean_r = sum(r.getdata()) / len(r.getdata())
    mean_g = sum(g.getdata()) / len(g.getdata())
    mean_b = sum(b.getdata()) / len(b.getdata())

    mean_rgb = {'red': mean_r, 'green': mean_g, 'blue': mean_b}

    max_mean_color = max(mean_rgb, key=lambda x:mean_rgb[x])
    if print_info:
        print(f'image is mostly: {max_mean_color}')
        return
    
    return max_mean_color
In [ ]:
# read image
img = Image.open(path_img + train_features.loc['ZJ000001'].filepath)

# crop image
cropped_image = crop_image_for_logo(img, plot_image=True)

# get main color image
get_img_main_color(cropped_image, print_info=True)
image is mostly: red

Tests¶

In [ ]: